const wu = require("./wuLib.js");
const {VM} = require('vm2');

function catchZGroup(code, groupPreStr, cb) {
    const debugPre = "(function(z){var a=11;function Z(ops,debugLine){";
    let zArr = {};
    for (let preStr of groupPreStr) {
        let content = code.slice(code.indexOf(preStr)), z = [];
        content = content.slice(content.indexOf("(function(z){var a=11;"));
        content = content.slice(0, content.indexOf("})(__WXML_GLOBAL__.ops_cached.$gwx")) + "})(z);";
        let vm = new VM({sandbox: {z: z, debugInfo: []}});
        vm.run(content);
        if (content.startsWith(debugPre)) for (let i = 0; i < z.length; i++) z[i] = z[i][1];
        zArr[preStr.match(/function gz\$gwx(\d*\_\d+)/)[1]] = z;
    }
    cb({"mul": zArr});
}

function catchZ(code, cb) {
    let groupTest = code.match(/function gz\$gwx(\d*\_\d+)\(\)\{\s*if\( __WXML_GLOBAL__\.ops_cached\.\$gwx\d*\_\d+\)/g);
    if (groupTest !== null) return catchZGroup(code, groupTest, cb);
    let z = [], vm = new VM({
        sandbox: {
            z: z,
            debugInfo: []
        }
    });
    let lastPtr = code.lastIndexOf("(z);__WXML_GLOBAL__.ops_set.$gwx=z;");
    if (lastPtr == -1) lastPtr = code.lastIndexOf("(z);__WXML_GLOBAL__.ops_set.$gwx");
    code = code.slice(code.lastIndexOf('(function(z){var a=11;function Z(ops){z.push(ops)}'), lastPtr + 4);
    vm.run(code);
    cb(z);
}

function restoreSingle(ops, withScope = false) {
    if (typeof ops == "undefined") return "";

    function scope(value) {
        if (value.startsWith('{') && value.endsWith('}')) return withScope ? value : "{" + value + "}";
        return withScope ? value : "{{" + value + "}}";
    }

    function enBrace(value, type = '{') {
        if (value.startsWith('{') || value.startsWith('[') || value.startsWith('(') || value.endsWith('}') || value.endsWith(']') || value.endsWith(')')) value = ' ' + value + ' ';
        switch (type) {
            case '{':
                return '{' + value + '}';
            case '[':
                return '[' + value + ']';
            case '(':
                return '(' + value + ')';
            default:
                throw Error("Unknown brace type " + type);
        }
    }

    function restoreNext(ops, w = withScope) {
        return restoreSingle(ops, w);
    }

    function jsoToWxon(obj) {//convert JS Object to Wechat Object Notation(No quotes@key+str)
        let ans = "";
        if (typeof obj === "undefined") {
            return 'undefined';
        } else if (obj === null) {
            return 'null';
        } else if (obj instanceof RegExp) {
            return obj.toString();
        } else if (obj instanceof Array) {
            for (let i = 0; i < obj.length; i++) ans += ',' + jsoToWxon(obj[i]);
            return enBrace(ans.slice(1), '[');
        } else if (typeof obj == "object") {
            for (let k in obj) ans += "," + k + ":" + jsoToWxon(obj[k]);
            return enBrace(ans.slice(1), '{');
        } else if (typeof obj == "string") {
            let parts = obj.split('"'), ret = [];
            for (let part of parts) {
                let atoms = part.split("'"), ans = [];
                for (let atom of atoms) ans.push(JSON.stringify(atom).slice(1, -1));
                ret.push(ans.join("\\'"));
            }
            return "'" + ret.join('"') + "'";
        } else return JSON.stringify(obj);
    }

    let op = ops[0];
    if (typeof op != "object") {
        switch (op) {
            case 3://string
                return ops[1];//may cause problems if wx use it to be string
            case 1://direct value
                return scope(jsoToWxon(ops[1]));
            case 11://values list, According to var a = 11;
                let ans = "";
                ops.shift();
                for (let perOp of ops) ans += restoreNext(perOp);
                return ans;
        }
    } else {
        let ans = "";
        switch (op[0]) {//vop
            case 2://arithmetic operator
            {
                function getPrior(op, len) {
                    const priorList = {
                        "?:": 4,
                        "&&": 6,
                        "||": 5,
                        "+": 13,
                        "*": 14,
                        "/": 14,
                        "%": 14,
                        "|": 7,
                        "^": 8,
                        "&": 9,
                        "!": 16,
                        "~": 16,
                        "===": 10,
                        "==": 10,
                        "!=": 10,
                        "!==": 10,
                        ">=": 11,
                        "<=": 11,
                        ">": 11,
                        "<": 11,
                        "<<": 12,
                        ">>": 12,
                        "-": len == 3 ? 13 : 16
                    };
                    return priorList[op] ? priorList[op] : 0;
                }

                function getOp(i) {
                    let ret = restoreNext(ops[i], true);
                    if (ops[i] instanceof Object && typeof ops[i][0] == "object" && ops[i][0][0] == 2) {
                        //Add brackets if we need
                        if (getPrior(op[1], ops.length) > getPrior(ops[i][0][1], ops[i].length)) ret = enBrace(ret, '(');
                        ;
                    }
                    return ret;
                }

                switch (op[1]) {
                    case"?:":
                        ans = getOp(1) + "?" + getOp(2) + ":" + getOp(3);
                        break;
                    case "!":
                    case "~":
                        ans = op[1] + getOp(1);
                        break;
                    case"-":
                        if (ops.length != 3) {
                            ans = op[1] + getOp(1);
                            break;
                        }//shoud not add more in there![fall through]
                    default:
                        ans = getOp(1) + op[1] + getOp(2);
                }
                break;
            }
            case 4://unkown-arrayStart?
                ans = restoreNext(ops[1], true);
                break;
            case 5://merge-array
            {
                switch (ops.length) {
                    case 2:
                        ans = enBrace(restoreNext(ops[1], true), '[');
                        break;
                    case 1:
                        ans = '[]';
                        break;
                    default: {
                        let a = restoreNext(ops[1], true);
                        //console.log(a,a.startsWith('[')&&a.endsWith(']'));
                        if (a.startsWith('[') && a.endsWith(']')) {
                            if (a != '[]') {
                                ans = enBrace(a.slice(1, -1).trim() + ',' + restoreNext(ops[2], true), '[');
                                //console.log('-',a);
                            } else {
                                ans = enBrace(restoreNext(ops[2], true), '[');
                            }
                        } else {
                            ans = enBrace('...' + a + ',' + restoreNext(ops[2], true), '[');//may/must not support in fact
                        }
                    }
                }
                break;
            }
            case 6://get value of an object
            {
                let sonName = restoreNext(ops[2], true);
                if (sonName._type === "var")
                    ans = restoreNext(ops[1], true) + enBrace(sonName, '[');
                else {
                    let attach = "";
                    if (/^[A-Za-z\_][A-Za-z\d\_]*$/.test(sonName)/*is a qualified id*/)
                        attach = '.' + sonName;
                    else attach = enBrace(sonName, '[');
                    ans = restoreNext(ops[1], true) + attach;
                }
                break;
            }
            case 7://get value of str
            {
                switch (ops[1][0]) {
                    case 11:
                        ans = enBrace("__unTestedGetValue:" + enBrace(jsoToWxon(ops), '['), '{');
                        break;
                    case 3:
                        ans = new String(ops[1][1]);
                        ans._type = "var";
                        break;
                    default:
                        throw Error("Unknown type to get value");
                }
                break;
            }
            case 8://first object
                ans = enBrace(ops[1] + ':' + restoreNext(ops[2], true), '{');//ops[1] have only this way to define
                break;
            case 9://object
            {
                function type(x) {
                    if (x.startsWith('...')) return 1;
                    if (x.startsWith('{') && x.endsWith('}')) return 0;
                    return 2;
                }

                let a = restoreNext(ops[1], true);
                let b = restoreNext(ops[2], true);
                let xa = type(a), xb = type(b);
                if (xa == 2 || xb == 2) ans = enBrace("__unkownMerge:" + enBrace(a + "," + b, '['), '{');
                else {
                    if (!xa) a = a.slice(1, -1).trim();
                    if (!xb) b = b.slice(1, -1).trim();
                    //console.log(l,r);
                    ans = enBrace(a + ',' + b, '{');
                }
                break;
            }
            case 10://...object
                ans = '...' + restoreNext(ops[1], true);
                break;
            case 12: {
                let arr = restoreNext(ops[2], true);
                if (arr.startsWith('[') && arr.endsWith(']'))
                    ans = restoreNext(ops[1], true) + enBrace(arr.slice(1, -1).trim(), '(');
                else ans = restoreNext(ops[1], true) + '.apply' + enBrace('null,' + arr, '(');
                break;
            }
            default:
                ans = enBrace("__unkownSpecific:" + jsoToWxon(ops), '{');
        }
        return scope(ans);
    }
}

function restoreGroup(z) {
    let ans = [];
    for (let g in z.mul) {
        let singleAns = [];
        for (let e of z.mul[g]) singleAns.push(restoreSingle(e, false));
        ans[g] = singleAns;
    }
    let ret = [];//Keep a null array for remaining global Z array.
    ret.mul = ans;
    return ret;
}

function restoreAll(z) {
    if (z.mul) return restoreGroup(z);
    let ans = [];
    for (let e of z) ans.push(restoreSingle(e, false));
    return ans;
}

module.exports = {
    getZ(code, cb) {
        catchZ(code, z => cb(restoreAll(z)));
    }
};
